" tests for listener_add() and listener_remove()

func s:StoreList(s, e, a, l)
  let s:start = a:s
  let s:end = a:e
  let s:added = a:a
  let s:text = getline(a:s)
  let s:list = a:l
endfunc

func s:AnotherStoreList(l)
  let s:list2 = a:l
endfunc

func s:EvilStoreList(l)
  let s:list3 = a:l
  call assert_fails("call add(a:l, 'myitem')", "E742:")
endfunc

func Test_listening()
  new
  call setline(1, ['one', 'two'])
  let s:list = []
  let id = listener_add({b, s, e, a, l -> s:StoreList(s, e, a, l)})
  call setline(1, 'one one')
  call listener_flush()
  call assert_equal([{'lnum': 1, 'end': 2, 'col': 1, 'added': 0}], s:list)

  " Undo is also a change
  set undolevels&  " start new undo block
  call append(2, 'two two')
  undo
  call assert_equal([{'lnum': 3, 'end': 3, 'col': 1, 'added': 1}], s:list)
  redraw
  " the two changes are not merged
  call assert_equal([{'lnum': 3, 'end': 4, 'col': 1, 'added': -1}], s:list)
  1

  " Two listeners, both get called.  Also check column.
  call setline(1, ['one one', 'two'])
  call listener_flush()
  let id2 = listener_add({b, s, e, a, l -> s:AnotherStoreList(l)})
  let s:list = []
  let s:list2 = []
  exe "normal $asome\<Esc>"
  redraw
  call assert_equal([{'lnum': 1, 'end': 2, 'col': 8, 'added': 0}], s:list)
  call assert_equal([{'lnum': 1, 'end': 2, 'col': 8, 'added': 0}], s:list2)

  " removing listener works
  call listener_remove(id2)
  call setline(1, ['one one', 'two'])
  call listener_flush()
  let s:list = []
  let s:list2 = []
  call setline(3, 'three')
  redraw
  call assert_equal([{'lnum': 3, 'end': 3, 'col': 1, 'added': 1}], s:list)
  call assert_equal([], s:list2)

  " a change above a previous change without a line number change is reported
  " together
  call setline(1, ['one one', 'two'])
  call listener_flush(bufnr())
  call append(2, 'two two')
  call setline(1, 'something')
  call bufnr()->listener_flush()
  call assert_equal([{'lnum': 3, 'end': 3, 'col': 1, 'added': 1},
	\ {'lnum': 1, 'end': 2, 'col': 1, 'added': 0}], s:list)
  call assert_equal(1, s:start)
  call assert_equal(3, s:end)
  call assert_equal(1, s:added)

  " an insert just above a previous change that was the last one does not get
  " merged
  call setline(1, ['one one', 'two'])
  call listener_flush()
  let s:list = []
  call setline(2, 'something')
  call append(1, 'two two')
  call assert_equal([{'lnum': 2, 'end': 3, 'col': 1, 'added': 0}], s:list)
  call listener_flush()
  call assert_equal([{'lnum': 2, 'end': 2, 'col': 1, 'added': 1}], s:list)

  " an insert above a previous change causes a flush
  call setline(1, ['one one', 'two'])
  call listener_flush()
  call setline(2, 'something')
  call append(0, 'two two')
  call assert_equal([{'lnum': 2, 'end': 3, 'col': 1, 'added': 0}], s:list)
  call assert_equal('something', s:text)
  call listener_flush()
  call assert_equal([{'lnum': 1, 'end': 1, 'col': 1, 'added': 1}], s:list)
  call assert_equal('two two', s:text)

  " a delete at a previous change that was the last one does not get merged
  call setline(1, ['one one', 'two'])
  call listener_flush()
  let s:list = []
  call setline(2, 'something')
  2del
  call assert_equal([{'lnum': 2, 'end': 3, 'col': 1, 'added': 0}], s:list)
  call listener_flush()
  call assert_equal([{'lnum': 2, 'end': 3, 'col': 1, 'added': -1}], s:list)

  " a delete above a previous change causes a flush
  call setline(1, ['one one', 'two'])
  call listener_flush()
  call setline(2, 'another')
  1del
  call assert_equal([{'lnum': 2, 'end': 3, 'col': 1, 'added': 0}], s:list)
  call assert_equal(2, s:start)
  call assert_equal('another', s:text)
  call listener_flush()
  call assert_equal([{'lnum': 1, 'end': 2, 'col': 1, 'added': -1}], s:list)
  call assert_equal('another', s:text)

  " the "o" command first adds an empty line and then changes it
  %del
  call setline(1, ['one one', 'two'])
  call listener_flush()
  let s:list = []
  exe "normal Gofour\<Esc>"
  redraw
  call assert_equal([{'lnum': 3, 'end': 3, 'col': 1, 'added': 1},
	\ {'lnum': 3, 'end': 4, 'col': 1, 'added': 0}], s:list)

  " Remove last listener
  let s:list = []
  call listener_remove(id)
  call setline(1, 'asdfasdf')
  redraw
  call assert_equal([], s:list)

  " Trying to change the list fails
  let id = listener_add({b, s, e, a, l -> s:EvilStoreList(l)})
  let s:list3 = []
  call setline(1, 'asdfasdf')
  redraw
  call assert_equal([{'lnum': 1, 'end': 2, 'col': 1, 'added': 0}], s:list3)

  eval id->listener_remove()
  bwipe!
endfunc

func s:StoreListArgs(buf, start, end, added, list)
  let s:buf = a:buf
  let s:start = a:start
  let s:end = a:end
  let s:added = a:added
  let s:list = a:list
endfunc

func Test_listener_args()
  new
  call setline(1, ['one', 'two'])
  let s:list = []
  let id = listener_add('s:StoreListArgs')

  " just one change
  call setline(1, 'one one')
  call listener_flush()
  call assert_equal(bufnr(''), s:buf)
  call assert_equal(1, s:start)
  call assert_equal(2, s:end)
  call assert_equal(0, s:added)
  call assert_equal([{'lnum': 1, 'end': 2, 'col': 1, 'added': 0}], s:list)

  " two disconnected changes
  call setline(1, ['one', 'two', 'three', 'four'])
  call listener_flush()
  call setline(1, 'one one')
  call setline(3, 'three three')
  call listener_flush()
  call assert_equal(bufnr(''), s:buf)
  call assert_equal(1, s:start)
  call assert_equal(4, s:end)
  call assert_equal(0, s:added)
  call assert_equal([{'lnum': 1, 'end': 2, 'col': 1, 'added': 0},
	\ {'lnum': 3, 'end': 4, 'col': 1, 'added': 0}], s:list)

  " add and remove lines
  call setline(1, ['one', 'two', 'three', 'four', 'five', 'six'])
  call listener_flush()
  call append(2, 'two two')
  4del
  call append(5, 'five five')
  call listener_flush()
  call assert_equal(bufnr(''), s:buf)
  call assert_equal(3, s:start)
  call assert_equal(6, s:end)
  call assert_equal(1, s:added)
  call assert_equal([{'lnum': 3, 'end': 3, 'col': 1, 'added': 1},
	\ {'lnum': 4, 'end': 5, 'col': 1, 'added': -1},
	\ {'lnum': 6, 'end': 6, 'col': 1, 'added': 1}], s:list)

  " split a line then insert one, should get two disconnected change lists
  call setline(1, 'split here')
  call listener_flush()
  let s:list = []
  exe "normal 1ggwi\<CR>\<Esc>"
  1
  normal o
  call assert_equal([{'lnum': 1, 'end': 2, 'col': 7, 'added': 1}], s:list)
  call listener_flush()
  call assert_equal([{'lnum': 2, 'end': 2, 'col': 1, 'added': 1}], s:list)

  call listener_remove(id)
  bwipe!

  " Invalid arguments
  call assert_fails('call listener_add([])', 'E921:')
  call assert_fails('call listener_add("s:StoreListArgs", [])', 'E730:')
  call assert_fails('call listener_flush([])', 'E730:')
endfunc

func s:StoreBufList(buf, start, end, added, list)
  let s:bufnr = a:buf
  let s:list = a:list
endfunc

func Test_listening_other_buf()
  new
  call setline(1, ['one', 'two'])
  let bufnr = bufnr('')
  normal ww
  let id = bufnr->listener_add(function('s:StoreBufList'))
  let s:list = []
  call setbufline(bufnr, 1, 'hello')
  redraw
  call assert_equal(bufnr, s:bufnr)
  call assert_equal([{'lnum': 1, 'end': 2, 'col': 1, 'added': 0}], s:list)

  call listener_remove(id)
  exe "buf " .. bufnr
  bwipe!
endfunc

func Test_listener_garbage_collect()
  func MyListener(x, bufnr, start, end, added, changes)
    " NOP
  endfunc

  new
  let id = listener_add(function('MyListener', [{}]), bufnr(''))
  call test_garbagecollect_now()
  " must not crash caused by invalid memory access
  normal ia
  call assert_true(v:true)

  call listener_remove(id)
  delfunc MyListener
  bwipe!
endfunc

" This verifies the fix for issue #4455
func Test_listener_caches_buffer_line()
  new
  inoremap <silent> <CR> <CR><Esc>O

  function EchoChanges(bufnr, start, end, added, changes)
    for l:change in a:changes
      let text = getbufline(a:bufnr, l:change.lnum, l:change.end-1+l:change.added)
    endfor
  endfunction
  let lid = listener_add("EchoChanges")
  set autoindent
  set cindent

  call setline(1, ["{", "\tif true {}", "}"])
  exe "normal /{}\nl"
  call feedkeys("i\r\e", 'xt')
  call assert_equal(["{", "\tif true {", "", "\t}", "}"], getline(1, 5))

  bwipe!
  delfunc EchoChanges
  call listener_remove(lid)
  iunmap <CR>
  set nocindent
endfunc

" Verify the fix for issue #4908
func Test_listener_undo_line_number()
  function DoIt()
    " NOP
  endfunction
  function EchoChanges(bufnr, start, end, added, changes)
    call DoIt()
  endfunction

  new
  let lid = listener_add("EchoChanges")
  call setline(1, ['a', 'b', 'c'])
  set undolevels&  " start new undo block
  call feedkeys("ggcG\<Esc>", 'xt')
  undo

  bwipe!
  delfunc DoIt
  delfunc EchoChanges
  call listener_remove(lid)
endfunc

func Test_listener_undo_delete_all()
  new
  call setline(1, [1, 2, 3, 4])
  let s:changes = []
  func s:ExtendList(bufnr, start, end, added, changes)
    call extend(s:changes, a:changes)
  endfunc
  let id = listener_add('s:ExtendList')

  set undolevels&  " start new undo block
  normal! ggdG
  undo
  call listener_flush()
  call assert_equal(2, s:changes->len())
  " delete removes four lines, empty line remains
  call assert_equal({'lnum': 1, 'end': 5, 'col': 1, 'added': -4}, s:changes[0])
  " undo replaces empty line and adds 3 lines
  call assert_equal({'lnum': 1, 'end': 2, 'col': 1, 'added': 3}, s:changes[1])

  call listener_remove(id)
  delfunc s:ExtendList
  unlet s:changes
  bwipe!
endfunc

func Test_listener_cleared_newbuf()
  func Listener(bufnr, start, end, added, changes)
    let g:gotCalled += 1
  endfunc
  new
  " check that listening works
  let g:gotCalled = 0
  let lid = listener_add("Listener")
  call feedkeys("axxx\<Esc>", 'xt')
  call listener_flush(bufnr())
  call assert_equal(1, g:gotCalled)
  %bwipe!
  let bufnr = bufnr()
  let b:testing = 123
  let lid = listener_add("Listener")
  enew!
  " check buffer is reused
  call assert_equal(bufnr, bufnr())
  call assert_false(exists('b:testing'))

  " check that listening stops when reusing the buffer
  let g:gotCalled = 0
  call feedkeys("axxx\<Esc>", 'xt')
  call listener_flush(bufnr())
  call assert_equal(0, g:gotCalled)
  unlet g:gotCalled

  bwipe!
  delfunc Listener
endfunc

func Test_col_after_deletion_moved_cur()
  func Listener(bufnr, start, end, added, changes)
    call assert_equal([#{lnum: 1, end: 2, added: 0, col: 2}], a:changes)
  endfunc
  new
  call setline(1, ['foo'])
  let lid = listener_add('Listener')
  call feedkeys("lD", 'xt')
  call listener_flush()
  bwipe!
  delfunc Listener
endfunc

" vim: shiftwidth=2 sts=2 expandtab
